<?php
/**
 * 
 * @todo description of this class
 * 
 * @author Alton Crossley <crossleyframework@nogahidebootstrap.com>
 * @package Crossley Framework
 *  
 * @copyright Copyright (c) 2003-2009, Nogaide BootStrap INC. All rights reserved.
 * @license BSD http://opensource.org/licenses/bsd-license.php
 * @version $Id:$
 * 
 */

/**
 * @namespace
 */
namespace X\Controller\Event;
use X\Controller\Event;
/**
 * this is a simple transport device for a HTTP Controller event
 * 
 * It offers the functionality of tracking the path prosession as well 
 * as where the controller is and request info
 *
 */
class Request implements Event
{
    /**
     * full path of the current running script
     * including appended path
     * 
     * Answers "Where are we currently at in
     * relation to domain root?"
     *
     * @var string
     */
    protected $_FullPath;
    /**
     * current query string
     *
     * @var string
     */
    protected $_UrlQuery;
    /**
     * base url with appended path removed
     * 
     * Answers "Where is the controller?"
     *
     * @var string
     */
    protected $_BaseUrl;
    /**
     * array of path crums to be processed
     *
     * @var array
     */
    protected $_Path;
    /**
     * array of processed path crums
     *
     * @var array
     */
    protected $_History = array();
    /**
     * array of submited data
     *
     * @var array
     */
    protected $_Data = array();
    /**
     * unfiltered values
     *
     * @var array
     */
    protected $_RawData = array();
    
    /**
     * unfiltered values
     *
     * @var array
     */
    protected $_SafeData;
    
    /**
     * unique id for this event based on data and request
     *
     * @var string
     */
    protected $_Uid;
    
    /**
     * request method was used
     * 
     * @var string
     */
    protected $_Method;

    function __construct($AutoBuild = true)
    {
        // @todo create authentication calback token lookup
        if ($AutoBuild)
        {
            $this->getMethod();
            $this->getPath();
            $this->_setData();
        }
    }
    
    /**
     * populate event data
     *
     * @param array $aData
     * @param bool $aData
     */
    public function setData(Array $Data, $PerformMerge = false)
    {
        if ($PerformMerge)
        {
            $this->_RawData = array_merge($this->_RawData, $Data);
        }
        else 
        {
            $this->_RawData = $Data;
        }
        
        $this->_SafeData = \X\Filter\Simple::Process($this->_RawData, true, array('_', '-', ' '));
    }
    /**
     * set data from Get and Post data
     * Allows for secondary variable
     * 
     * @param bool $Force
     */
    protected function _setData($Force = false)
    {
        if (empty($this->_Data) || $Force)
        {
            $Secondary = $this->_getSecondaryValues();
            $this->setData(array_merge($_GET, $_POST, $Secondary));
        }
    }
    /**
     * meant to handle cases when your rewrite must contain an extra '?'
     * ex index.php?/some/path?some=info&more=values 
     */
    protected function _getSecondaryValues()
    {
    	$QueryString = (strlen($_SERVER["QUERY_STRING"]) > strlen($_SERVER["REQUEST_URI"])) ? $_SERVER["QUERY_STRING"] : $_SERVER["REQUEST_URI"];
    	// only act on multiple seperating questionmarks
    	if (substr_count($QueryString, '?') > 1)
    	{
    		$Return = array();
    		$SubQueryString = substr($QueryString, strpos($QueryString, '?')+1);
    		
    		$Queries = explode('?', $SubQueryString);
    		$KeyValuePairs = explode('&', $Queries[1]);
    		foreach ($KeyValuePairs as $Pair)
    		{
    			$KeyValue = explode('=', $Pair);
    			if (count($KeyValue) > 1)
    			{
    			    $Return[$KeyValue[0]] = $KeyValue[1];    			    
    			}
    		}
    		return $Return;
    	}
    	
    	return array();
    }
    
    /**
     * Interface to event data
     * returns a string if a key is supplied
     * 'data' can be submited GPC data
     *
     * @return string|array
     */
    public function getData($Key = null)
    {
        $this->_setData();
        
        if ($Key)
        {
            if (array_key_exists($Key, $this->_SafeData))
            {
                $bFiltered = false;
                $Filtered = $this->_RawData[$Key];
                //@todo handle lazy filtering of raw data using a given filter array
                return ($bFiltered) ? $Filtered : $this->_SafeData[$Key];
            }
            
            return false;
        }
        
        return $this->_Data;
    }
    
    /**
     * Interface to unfiltered event data
     * returns a string if a key is supplied
     * 'data' can be submited GPC data
     *
     * @return string|array
     */
    public function getRawData($Key = null)
    {
        $this->_setData();
        
        if ($Key)
        {
            if (array_key_exists($Key, $this->_RawData))
            {
                return $this->_RawData[$Key];
            }
            
            return false;
        }
        
        return $this->_RawData;
    }
    
    /**
     * Interface to unfiltered event data
     * returns a string if a key is supplied
     * 'data' can be submited GPC data
     *
     * @return string|array
     */
    public function getSafeData($Key = null)
    {
        $this->_setData();
        
        if ($Key)
        {
            if (array_key_exists($Key, $this->_SafeData))
            {
                return $this->_SafeData[$Key];
            }            
            return false;
        }
        
        return $this->_SafeData;
    }
    
    // -- End Event Data Management
    
    /**
     * @return array
     */
    public function getHistory()
    {
        return $this->_History;
    }

    /**
     * get the equivelent of ../ on the url
     * returns a relative path string to the directory above the current one
     *
     * @param int $iCount number of url directories to traverse
     * @return unknown
     */
    public function getParentUrl($Count = null)
    {
        $Count = (int)$Count;
        $History = $this->getHistory();
        
        if (!$Count)
        {
            $Count = count($History)-1;
        }
        
        $ParentHistory = array();
        for ($c = 0; $c < $Count; $c++)
        {
            if (array_key_exists($c, $History))
                $ParentHistory[] = $History[$c];
        }
        
        return '/'.implode('/', $ParentHistory);
    }
    
    /**
     * @return string
     */
    public function getBaseUrl()
    {
        return $this->_BaseUrl;
    }
    
    /**
     * @return string
     */
    public function getFullPath()
    {
        return $this->_FullPath;
    }
    
    /**
     * @return string
     */
    public function getUrlQuery()
    {
        return $this->_UrlQuery;
    }
    
    
    
    /**
     * @param array $History
     */
    public function setHistory($History)
    {
        $this->History = $History;
    }
    
    public function setSetBaseUrl($BaseUrl = null)
    {
        if (empty($BaseUrl))
        {
            $this->_BaseUrl = parse_url(str_replace($this->_FullPath, '', $_SERVER['PHP_SELF']));
            $this->_BaseUrl = $this->_BaseUrl['path'];
        }
        else
        {
            $this->_BaseUrl = $BaseUrl;
        }
    }
    
    /**
     * @param string $_FullPath
     */
    public function setFullPath($FullPath)
    {
        $this->_FullPath = $FullPath;
    }
    
    /**
     * @param string $_UrlQuery
     */
    public function setUrlQuery($UrlQuery)
    {
        $this->_UrlQuery = $UrlQuery;
    }
    
    public function getHtmlEncoded($Key = null)
    {
        $this->_setData();
        
        if ($Key)
        {
            if (array_key_exists($Key, $this->_RawData))
            {
                return Filter\HtmlEntities::go($this->_RawData[$Key]);
            }
            
            return false;
        }
        
        return Filter\HtmlEntities::go($this->_RawData);
    }
    
    /**
     * returns the path beyond the controller, and returns 
     * the crumbs in an array
     * 
     * @param bool $bRefresh force refresh
     * @return array
     */
    public function getPath($Refresh = false)
    {
        if (empty($this->_Path) || $Refresh)
        {
            $this->_resetPathArray($this->getPathInfo());
        }
        
        return $this->_Path;
    }
    
    /**
     * set the internal path array and reset history array
     *
     * @param string $PathInfo
     */
    private function _resetPathArray($PathInfo)
    {
        $this->_Path = array();
        $this->_History = array();
        
        if (empty($PathInfo))
        {
            $this->_Path = array('index' , 'index');
        }
        else
        {
            if (\strpos($PathInfo, '?'))
            {
                $PathInfo = \substr($PathInfo, 0, strpos($PathInfo, '?'));
            }

            $this->_Path = explode('/', $PathInfo);
            if (empty($this->_Path[0]))
            {
                unset($this->_Path[0]);
            }
        }
        
        if (count($this->_Path) == 1)
        {
            array_push($this->_Path, 'index');
        }
        
        $this->_Path = \X\Filter\Simple::process($this->_Path, true, array('.', '_', '-', ' '));
    }
    
    /**
     * @param array $_Path
     */
    public function setPath(Array $Path = null)
    {
        if (empty($Path))
        {
            // if we passed nothing in, we need to set it
            $this->getPath(true);
        } else if (is_array($Path))
        {
            $this->_Path = $Path;
        } else
        {
            $this->_Path = explode('/', (string) $xPath);
        }
    }
    /**
     * move path position back to the begining
     *
     */
    public function resetPath()
    {
        $this->_Path = array_merge($this->_History, $this->_Path);
        $this->_History = array();
        return $this->_Path;
    }
    
    /**
     * return an instance of the current class
     * /MAY/ be an singelton, but not a requirement
     *
     * @return __CLASS__
     */
    static public function getInstance()
    {
        $MyClass = __CLASS__;
        $Instance = new $MyClass();
        return $Instance;
    }
    
    /**
     * obtains the path from the $_ENV or $_SERVER predefined arrays
     * 
     * responsible for setting $_sBaseUrl, $_sUrlQuery, $_sFullPath
     * 
     * @todo clean getPathInfo() up
     *
     * @return string|false if not set in either array
     */
    public function getPathInfo($Path = null)
    {
        $PathInfo = '';
        $ParseUrl = array();
        
        if (empty($Path))
        {
            switch (true)
            {
                // IIS URL rewrite
                case (array_key_exists('HTTP_X_REWRITE_URL', $_SERVER)):
                        $PathInfo = $_SERVER['HTTP_X_REWRITE_URL'];
                        break;
                // IIS URL rewrite
                case (array_key_exists('IIS_WasUrlRewritten', $_SERVER)
                    && $_SERVER['IIS_WasUrlRewritten'] == '1'
                    && array_key_exists('UNENCODED_URL', $_SERVER)
                    && $_SERVER['UNENCODED_URL'] != ''):
                        $PathInfo = $_SERVER['UNENCODED_URL'];
                        break;
                case (array_key_exists('PATH_INFO', $_SERVER) && !empty($_SERVER['PATH_INFO'])):
                        $PathInfo = $_SERVER['PATH_INFO'];
                        break;
                case (array_key_exists('PATH_INFO', $_ENV) && !empty($_ENV['PATH_INFO'])):
                        $PathInfo = $_ENV['PATH_INFO'];
                        break;
            }

            if (array_key_exists('REQUEST_URI', $_SERVER))
            {
                $ParseUrl = $this->_parseUrl($_SERVER['REQUEST_URI']);
            } 
            else if (array_key_exists('SCRIPT_URL', $_SERVER))
            {
                $ParseUrl = $this->_parseUrl($_SERVER['SCRIPT_URL']);
            }

            
        }
        else 
        {
            $PathInfo = $Path;
            $ParseUrl = $this->_parseUrl($Path);
        }

        if (array_key_exists('query', $ParseUrl))
        {
            $this->_UrlQuery = $ParseUrl['query'];
        }
        
        $this->_FullPath = $ParseUrl['path'];
        
        if (empty($this->_BaseUrl))
        {
            $this->setSetBaseUrl(\str_replace('\\', '/', dirname($_SERVER["SCRIPT_NAME"])));
        }

        // remove the script name from the path if present
        $ScriptName = $_SERVER["SCRIPT_NAME"];
        if (strpos($PathInfo, $_SERVER["SCRIPT_NAME"]) === 0)
        {
            $requestUri = substr($PathInfo, strlen($ScriptName));
        }
        
        return (strlen(trim($PathInfo)) > 1) ? $PathInfo : false;
    }
    
    private function _parseUrl($sUrl)
    {
    	$aMatches = array();
    	//$iMatchCount = preg_match_all('/?P<name>.*?\?/', $sUrl, $aMatches);
    	$iMatchCount = preg_match('|^(?=[^&])(?:(?<scheme>[^:/?#]+):)?(?://(?<authority>[^/?#]*))?(?<path>[^?#]*)(?:\?(?<query>[^#]*))?(?:#(?<fragment>.*))?|', $sUrl, $aMatches);
    	return $aMatches;
    }
   
    function setUri($sUri)
    {
        $sPath = $this->getPathInfo($sUri);
        $this->_resetPathArray(trim($sPath));
    }
    
    /**
     * move a path event from path to history
     *
     * @param array $aEvent
     * @return string
     */
    public function getStep()
    {
        if (empty($this->_Path))
        {
            return false;
        }
        
        if (is_array($this->_Path))
        {
            $sStep = array_shift($this->_Path);
            array_push($this->_History, $sStep);
            
            return $sStep;
        }
        
        return false;
    }
    /**
     * @see X\Broker\Event::getUid()
     *
     */
    public function getUid($bRebuild = false)
    {
        if (isset($this->_sUid) && !$bRebuild) return $this->_sUid;
        
        $this->_sUid = md5(var_export($this->toArray(), true));
        
        return $this->_sUid;
    }
    
    /**
     * object attributes to an array
     * 
     */
    public function toArray()
    {
        $aReturn = array();
        
        $aReturn['base_url'] = $this->_BaseUrl;
        $aReturn['full_path'] = $this->_FullPath;
        
        $aReturn['data'] = $this->_RawData;
        $aReturn['history'] = $this->_History;
        $aReturn['path'] = $this->_Path;
        $aReturn['path'] = $this->_FullPath;
        
        $aReturn['parent_url'] = $this->getParentUrl();
        $aReturn['uid'] = $this->getUid();
        
        return $aReturn;
    }
    /**
     * return the request method for the current request
     * 
     */
    public function getMethod()
    {
        if (!isset($this->_Method))
        {
            $this->_Method = $_SERVER["REQUEST_METHOD"];
        }
        
        return $this->_Method;
    }
    /**
     * true if the request method was GET
     * 
     * @return bool
     */
    public function isGet()
    {
        return ('GET' == $this->getMethod());
    }
    /**
     * true if the request method was HEAD
     * 
     * @return bool
     */
    public function isHead()
    {
        return ('HEAD' == $this->getMethod());
    }
    /**
     * true if the request method was POST
     * 
     * @return bool
     */
    public function isPost()
    {
        return ('POST' == $this->getMethod());
    }
    /**
     * true if the request method was PUT
     * 
     * @return bool
     */
    public function isPut()
    {
        return ('PUT' == $this->getMethod());
    }

}